/*
 *  Voc.cpp
 *  gre
 *
 *  Created by Kutlak Roman on 10/03/2012.
 *  Copyright 2012 Kutlak Roman. All rights reserved.
 *
 */

#include <iostream>
#include <limits>

#include "Voc.h"

using std::cout;
using std::cerr;
using std::endl;
using std::string;

namespace kutlak {
namespace roman {
namespace gre {
    
    
    
    // class implemented
    /////////////////////////////// PUBLIC   ///////////////////////////////////
    
    std::once_flag Voc::sFlag;
    
#pragma mark -
#pragma mark lifecycle
    Voc::Voc() : nWords(0)
    {    
        mMap.emplace("", nWords);
    }
    
    Voc::~Voc() noexcept(true) = default;
    
    Voc& Voc::Instance() 
    {
        static std::unique_ptr<Voc> sInstance;
        
        std::call_once(sFlag, []()
                       {
                           sInstance = std::unique_ptr<Voc>(new Voc());
                       });
        
        return *sInstance;
    }
    
    void swap(Voc& left, Voc& right) noexcept(true) 
    {
        using std::swap;
        swap(left.nWords, right.nWords);
        swap(left.mMap, right.mMap);
    }
    
#pragma mark -
#pragma mark I/O
    std::ostream& operator<<(std::ostream& os, const Voc& v) 
    {
        v.Serialise(os);
        return os;
    }
    
    std::istream& operator>>(std::istream& is, Voc& v) 
    {
        Voc tmp;
        tmp.Deserialise(is);
        swap(v, tmp);
        return is;
    }
    
    void Voc::Serialise(std::ostream& os) const 
    {
        os << "<vocab>\n";
        os << nWords << endl;
        
        os << "<words>\n";
        for (auto& item : mMap) 
        {
            os << item.first << endl;
            os << item.second << endl;
        }
        os << "</words>\n";
        
        os << "</vocab>\n";
    }
    
    void Voc::Deserialise(std::istream& is) 
    {
        string tmp;
        is >> tmp;
        if ("<vocab>" != tmp) 
        {
            string message = "Expected <vocab>.";
            throw DeserialisationException(std::move(message),
                                           std::move(tmp));
        }
        
        Clear();
        // read numbers
        if (!(is >> nWords)) 
        {
            string message = "Could not read data.";
            throw DeserialisationException(std::move(message));
        }
        
        // check that attributes follow
        if (!(is >> tmp) || "<words>" != tmp) 
        {
            string message = "Expected <words>.";
            throw DeserialisationException(std::move(message),
                                           std::move(tmp));
        }
        
        // read the attributes
        while (getline(is, tmp) && tmp != "</words>") 
        {
            Vindex_t val;
            is >> val >> std::ws; // remove the enter at the end of the line
            mMap.emplace(std::move(tmp), val);
        }
        
        // read end marker
        if (!(is >> tmp && "</vocab>" == tmp)) 
        {
            string message = "Expected </vocab>.";
            throw DeserialisationException(std::move(message),
                                           std::move(tmp));
        }
    }
    
#pragma mark -
#pragma mark operations
    //=============================  OPERATIONS  ===============================
    
    Vindex_t Voc::Index(const std::string& str) 
    {
        if ("" == str)
            return 0;
        
        if (std::numeric_limits<Vindex_t>::max() == nWords)
            throw IndexException("Index too large.");
        
        Vindex_t tmp = nWords + 1;
        auto it = mMap.insert(std::make_pair(str, tmp));
        if (it.second)
        { // did the insertion succede?
            return ++nWords;
        }
        else 
        {
            return it.first->second;
        }
    }
    
    Vindex_t Voc::Index(std::string&& str) 
    {
        if ("" == str)
            return 0;
        
        if (std::numeric_limits<Vindex_t>::max() == nWords)
            throw IndexException("Index too large.");
        
        Vindex_t tmp = nWords + 1;
        auto it = mMap.emplace(std::move(str), tmp);
        if (it.second)
        { // did the insertion succede?
            return ++nWords;
        }
        else 
        {
            return it.first->second;
        }
    }
    
    std::string Voc::String(const Vindex_t& idx) const 
    {
        std::string result = "";
        
        for (const auto& item : mMap) 
        {
            const std::string& key = item.first;
            const Vindex_t& val = item.second;
            
            if (val == idx)
                return key;
        }
        
        return result;
    }
    
    
} // end namespace gre
}
}
